#include "LFilePack.h"
#include <stdio.h>
#include <iostream>
#include <ios>
#include <fstream>

#include "LDirEnum.h"
#include <direct.h>
#include <stdlib.h>
#include <stdio.h>


int getFileLen(const wchar_t * file_name)
{
	FILE* file = _wfopen(file_name,L"rb");
	int pos = ftell(file);
	fseek(file,0,SEEK_END);
	int len = ftell(file) - pos;
	fclose(file);
	return len;
}

int getFileBuffer(const wchar_t * file_name, char** ucs16Buff)
{
	long length = 0;
	FILE* file = _wfopen(file_name, L"rb");
	if(file != NULL)
	{

		fseek( file, 0, SEEK_END );
		length = ftell( file );
		fseek( file ,0, SEEK_SET );
		*ucs16Buff = new char[length] ;
		memset( *ucs16Buff , 0 , length);


		if ( fread( *ucs16Buff,  1, length, file ) != length ) 
		{
			delete [] *ucs16Buff;
			*ucs16Buff = NULL;
			return length;

		}

	}
	fclose(file);
	return length;
}
std::wstring getFileName(const wchar_t* fullName)
{
	if(fullName == NULL)
		return L"";
	int idx = (int)wcslen(fullName);
	for(; idx >= 0 ; idx --)
	{
		if(fullName[idx] == '\\' || fullName[idx]=='/')
		{
			break;
		}
	}
	std::wstring _ret = fullName + (idx + 1);   
	return _ret;
}

std::wstring getPathName(const wchar_t* fullName)
{
	if(fullName == NULL)
		return L"";
	std::wstring _ret = fullName;   
	std::wstring::size_type pos = _ret.rfind('\\');
	if(pos == std::wstring::npos) 
	{
		pos = _ret.rfind('/');
	}
	if(pos == std::wstring::npos )
	{
		return L"";
	}

	_ret.replace(pos ,wcslen(fullName), L"");
	return _ret + PATH_SPLITTER_STRING;
}


bool CLFilePack::__insert_file( const wchar_t* name, const wchar_t* full_name , lfp_data_type type, int compress_rate )
{
	if ( NULL == full_name )
		return false;
	return __insert_data( name, full_name, type, compress_rate);
}


bool CLFilePack::__insert_buffer( const wchar_t* name, int compress_rate )
{
		return __insert_data( name, NULL, lfpdt_dataBuffer, compress_rate);
}


bool CLFilePack::__insert_data( const wchar_t* name,  const wchar_t* full_name, lfp_data_type type, int compress_rate )
{
	if ( NULL == full_name && lfpdt_dataBuffer != type )
		return false;
	size_t nSize = m_listDataEntry.size();
	if(nSize >= m_FileHeader.m_nHashTableSize)
	{
		__resize_hash_table((int)m_listDataEntry.size()*2);
	}
	sHashValue hashValue;
	CLHashDecryptTable::singleton().string_hash(name,hashValue);
	//calculate the item index in the hash table;
	int hashTableIndex = hashValue.indexHash % m_FileHeader.m_nHashTableSize;
	for(unsigned int i = 0 ; i < m_FileHeader.m_nHashTableSize ; ++i)
	{
		int nStartAddr = 0;
		if ( 0 != i )
		{
			nStartAddr = m_listDataEntry[i-1].m_DataEntryInfo.m_nStart + m_listDataEntry[i-1].m_DataEntryInfo.m_nLength;
		}
		int index = (hashTableIndex + i)%m_FileHeader.m_nHashTableSize;
		if(m_pHashTable[index].m_Flag == HASHFLAG_NOT_USED )
		{
			int iDataItem = (int) m_listDataEntry.size();
			sFPDataEntry newentry;
			m_listDataEntry.push_back(newentry);

			sFPDataEntry& entry = m_listDataEntry[m_listDataEntry.size() - 1];

			m_pHashTable[index].m_HashIndex  = hashValue.indexHash ;
			m_pHashTable[index].m_HashValue1 = hashValue.valueA;
			m_pHashTable[index].m_HashValue2 = hashValue.valueB;
			m_pHashTable[index].m_FPIndex    = iDataItem;
			m_pHashTable[index].m_Flag       = HASHFLAG_COMMON;
			entry.m_DataEntryInfo.m_nHashTableIndex = index;
			entry.m_DataEntryInfo.m_nStart = nStartAddr;
			switch(type)
			{
			case lfpdt_file:
				{
					entry.m_data = NULL;
					wcscpy(entry.m_full_file_name, full_name);
					entry.m_DataEntryInfo.m_nLength = getFileLen(full_name);
					wstring strName = L"";
					if ( NULL == name )
					{
						strName = getFileName(full_name);
						wcscpy(entry.m_DataEntryInfo.m_name, strName.c_str());
					}
					else
						wcscpy(entry.m_DataEntryInfo.m_name, name);


				}
				break;
			case lfpdt_dir:
				{
					if ( NULL == name )
					{
						/// wait 
					}
					else
						wcscpy(entry.m_DataEntryInfo.m_name, name);

					entry.m_DataEntryInfo.m_nLength = 0;
				}
				break;
			case lfpdt_dataBuffer:
				{
					entry.m_DataEntryInfo.m_nLength = 0;

				}
				break;
			default:
				break;
			}
			return true;
		}
	}
	return false;
}


bool CLFilePack::_write_data_to_file(char* pData, const wchar_t* file_name, int nLength )
{
	FILE* file = _wfopen(file_name , L"wb");
	if(file == NULL)
	{
		return false;
	}
	else
	{
		fwrite(pData, sizeof(char), nLength, file );
	}
	fclose(file);
	return true;
}

bool CLFilePack::_unpack_data(ifstream & stream, const wchar_t* file_name, sFPDataEntryInfo& entryInfo )
{
	if ( NULL == file_name )
		return false;
	stream.seekg( entryInfo.m_nStart,ios::beg );

	char* pData  = NULL;
	pData = new char[entryInfo.m_nLength];
	int nLength = entryInfo.m_nLength;
	memset(pData, 0, nLength);

	stream.read( pData, entryInfo.m_nLength);
	return _write_data_to_file(pData, file_name, entryInfo.m_nLength);
}

bool CLFilePack::__open_for_read(const wchar_t* file_name)
{
	if ( NULL == file_name )
		return false;
	ifstream  file;
	file.open(file_name , ios::binary);
	if(file.fail())
		return false;
	return __open_for_read(file);
}

bool CLFilePack::__open_for_read(ifstream & stream)
{
	//// read file header
	stream.read((char*)&m_FileHeader,sizeof(sFPFileHeader));
	/// read hash table 
	m_pHashTable = new sFPHashTableItem[m_FileHeader.m_nHashTableSize];
	stream.read((char*)m_pHashTable,m_FileHeader.m_nHashTableSize*sizeof(sFPHashTableItem));
	/// 
	if ( stream.eof() )
		return false;
	/// read data entry table
	size_t entryTableSize = m_FileHeader.m_nFpTableSize;
	m_listDataEntry.reserve(entryTableSize);
	m_listDataEntry.resize( entryTableSize );
	for ( size_t i=0; i<m_listDataEntry.size(); i++ )
	{
		sFPDataEntry& entry = m_listDataEntry[i];

		stream.read((char*)&entry.m_DataEntryInfo, sizeof(sFPDataEntryInfo));

	}
	return true;

}


bool CLFilePack::_fill_header()
{
	m_FileHeader.m_nFpTableSize = m_listDataEntry.size();
	return true;
}

bool CLFilePack::_write_data_to_stream(ostream& stream)
{
	long nStart = __calDataEntryPoint();
	stream.write((const char*)&m_FileHeader,sizeof(sFPFileHeader));

	stream.write((const char*)&m_pHashTable, m_FileHeader.m_nHashTableSize*sizeof(sFPHashTableItem));
	for ( size_t i=0; i<m_listDataEntry.size(); i++ )
	{
		sFPDataEntry& entry = m_listDataEntry[i];
		stream.write((const char*)&entry.m_DataEntryInfo,sizeof(sFPDataEntryInfo));

	}


	for ( size_t i=0; i<m_listDataEntry.size(); i++ )
	{
		sFPDataEntry& entry = m_listDataEntry[i];
		if ( entry.m_DataEntryInfo.m_nLength >0 )
		{
			if ( entry.m_bBuffer )
			{

			}
			else
			{
				getFileBuffer(entry.m_full_file_name, &entry.m_data );
				stream.write( (const char*)entry.m_data, entry.m_DataEntryInfo.m_nLength);
				delete [] entry.m_data;
				entry.m_data = NULL;
			}

		}
	}

	return true;

}


/// hashtable
void CLFilePack::__init_hash_table(int hash_table_size)
{
		/*
	Alloc the hash talbe buffer;
	*/
	if(m_pHashTable != NULL)
	{
		delete [] m_pHashTable; 
		m_pHashTable = NULL;
	}
	m_pHashTable = new sFPHashTableItem [hash_table_size];
	m_FileHeader.m_nHashTableSize = hash_table_size;
	__clear_hash_table();
}


void CLFilePack::__clear_hash_table()
{
		/*
	Clear the whole hash table.
	*/
	assert(m_pHashTable);
	int nHashTable = m_FileHeader.m_nHashTableSize;
	for(int i = 0 ; i < nHashTable ; ++ i)
	{
		m_pHashTable[i].m_FPIndex    = -1;
		m_pHashTable[i].m_Flag       = HASHFLAG_NOT_USED;//Not used
		m_pHashTable[i].m_HashIndex  = -1;
		m_pHashTable[i].m_HashValue1 = -1;
		m_pHashTable[i].m_HashValue2 = -1;
	}  
}

void CLFilePack::__resize_hash_table(int hash_table_size)
{
	__init_hash_table(hash_table_size);
	__recalculate_hash_table();
}

void CLFilePack::__recalculate_hash_table()
{
	sHashValue hashValue;
	size_t nDataItem = m_listDataEntry.size();
	for(size_t iDataItem = 0 ; iDataItem < nDataItem ; ++ iDataItem)
	{
		sFPDataEntry& entry = m_listDataEntry[iDataItem];

		const wchar_t* name = entry.m_DataEntryInfo.m_name;
		CLHashDecryptTable::singleton().string_hash(name,hashValue);
		//calculate the item index in the hash table;
		int hashTableIndex = hashValue.indexHash % m_FileHeader.m_nHashTableSize;
		for(unsigned int k = 0 ; k < m_FileHeader.m_nHashTableSize ; ++k)
		{
			int index = (hashTableIndex + k)%m_FileHeader.m_nHashTableSize;
			if(m_pHashTable[index].m_Flag == HASHFLAG_NOT_USED )
			{
				m_pHashTable[index].m_HashIndex  = hashValue.indexHash ;
				m_pHashTable[index].m_HashValue1 = hashValue.valueA;
				m_pHashTable[index].m_HashValue2 = hashValue.valueB;
				m_pHashTable[index].m_FPIndex    = (int)iDataItem;
				m_pHashTable[index].m_Flag       = HASHFLAG_COMMON;
				entry.m_DataEntryInfo.m_nHashTableIndex = index;
				break;
			}
		}   
	}
}

void CLFilePack::__fit_hash_table()
{
	if((size_t)m_FileHeader.m_nHashTableSize > m_listDataEntry.size() * 2)
		return ;
	__resize_hash_table(m_FileHeader.m_nHashTableSize*2);
}


long CLFilePack::__calDataEntryPoint()
{
	long nStart = sizeof(sFPFileHeader);
	nStart += m_FileHeader.m_nHashTableSize * sizeof(sFPHashTableItem);
	nStart += m_listDataEntry.size() * sizeof(sFPDataEntryInfo);

	for ( size_t i=0; i<m_listDataEntry.size(); i++ )
	{
		sFPDataEntry& entry = m_listDataEntry[i];
		entry.m_DataEntryInfo.m_nStart += nStart;
	}
	return nStart;
}



